using System;
using System.Threading;
using System.Runtime.InteropServices;

namespace PocketPCSideShowClient
{
    public enum PowerMode
    {
        Unknown = 0,
        PowerOn = 0x10000,
        PowerOff = 0x20000,
        PowerCritical = 0x40000,
        PowerBoot = 0x80000,
        PowerIdle = 0x100000,
        PowerSuspend = 0x200000,
        PowerReset = 0x800000
    };


    public delegate void PowerChangeDelegate(PowerMode newMode);


    /// <summary>
    /// Summary description for PowerNotifications.
    /// </summary>
    public class PowerNotifications
    {
        private IntPtr _queuePtr = IntPtr.Zero;
        private IntPtr _notificationHandle = IntPtr.Zero;
        private Thread _workerThread;
        private bool _stopWorker = false;

        const uint STATUS_WAIT_0 = 0x0;
        const int MAXTIME = 200;
        const int MAXBUFFER = 16;
        PowerMode _currentState = PowerMode.Unknown;

        public event PowerChangeDelegate OnPowerChange;

        #region Method imports
        [DllImport("coredll.dll")]
        private static extern bool StopPowerNotifications(IntPtr handle);

        [DllImport("coredll.dll")]
        private static extern IntPtr RequestPowerNotifications(IntPtr hMsgQ, uint Flags);

        [DllImport("coredll.dll")]
        private static extern uint WaitForSingleObject(IntPtr hHandle, int wait);

        [DllImport("coredll.dll")]
        private static extern IntPtr CreateMsgQueue(string name, ref MsgQOptions options);

        [DllImport("coredll.dll")]
        private static extern bool CloseMsgQueue(IntPtr handle);

        [DllImport("coredll.dll")]
        private static extern bool ReadMsgQueue(IntPtr hMsgQ, byte[] lpBuffer, uint cbBufSize, ref uint lpNumRead, int dwTimeout, ref uint pdwFlags);
        #endregion

        public PowerNotifications()
        {
            MsgQOptions options = new MsgQOptions();
            options.dwFlags = 0;
            options.dwMaxMessages = 20;
            options.cbMaxMessage = 10000;
            options.bReadAccess = true;
            options.dwSize = (uint)Marshal.SizeOf(options);
            //The message queue is used to receive power notifications.
            _queuePtr = CreateMsgQueue(null, ref options);
            _notificationHandle = RequestPowerNotifications(_queuePtr, 0xFFFFFFFF);
            //The worker thread will read messages from the queue and if the power state has changed,
            //an event will be dispatched.
            _workerThread = new Thread(new ThreadStart(DoWork));
        }

        public void Start()
        {
            //Start the worker thread
            _workerThread.Start();
        }

        public void Stop()
        {
            //Stop the worker thread
            _stopWorker = true;
            StopPowerNotifications(_notificationHandle);
            CloseMsgQueue(_queuePtr);
        }

        private void DoWork()
        {
            byte[] buf = new byte[MAXBUFFER];
            uint nRead = 0, flags = 0, res = 0;
            try
            {
                while (!_stopWorker)
                {
                    //Lets wait MAXTIME for new messages written to the queue
                    res = WaitForSingleObject(_queuePtr, MAXTIME);
                    if (res == STATUS_WAIT_0)
                    {
                        //Read the message from the queue and determine the power state.
                        if (ReadMsgQueue(_queuePtr, buf, (uint)buf.Length, ref nRead, 0, ref flags))
                        {
                            PowerMode pm = (PowerMode)ConvertByteArray(buf, 4);
                            if ((pm != PowerMode.Unknown) && (OnPowerChange != null) && _currentState != pm)
                            {
                                //Storing and checking the current state prevents the same event from
                                //occuring multiple times.
                                _currentState = pm;
                                OnPowerChange(pm);
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {
                //Don't spoil our day :-)
            }
        }

        private uint ConvertByteArray(byte[] array, int offset)
        {
            uint res = 0;
            res += array[offset];
            res += array[offset + 1] * (uint)0x100;
            res += array[offset + 2] * (uint)0x10000;
            res += array[offset + 3] * (uint)0x1000000;
            return res;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MsgQOptions
        {
            public uint dwSize;
            public uint dwFlags;
            public uint dwMaxMessages;
            public uint cbMaxMessage;
            public bool bReadAccess;
        }
    }
}